// $Id: IdentityMap.cs 10 2008-02-29 15:58:02Z mcartoixa $
/*
 *  Copyright (C) 2007 Noury Solutions
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */
#pragma warning disable 1587
/** \file
 * \brief Generic Identity Maps.
 *
 * \author $Author: mcartoixa $
 * \date $Date: 2008-02-29 16:58:02 +0100 (ven., 29 févr. 2008) $
 * \version $Revision: 10 $
 */
#pragma warning restore 1587

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace Salamanca.DataAccess
{



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Generic <include file="../doc/Global.doc.xml" path="doc/template[@name='IdentityMap']/*" />.</summary>
    /// <remarks>
    ///   <para>This <include file="../doc/Global.doc.xml" path="doc/template[@name='IdentityMap']/*" /> stores
    /// <see cref="WeakReference">weak references</see> to the objects stored. A background thread is
    /// created, which removes the inactive <see cref="WeakReference">weak references</see> from the
    /// <include file="../doc/Global.doc.xml" path="doc/template[@name='IdentityMap']/*" />.</para>
    /// </remarks>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public sealed class IdentityMap<TKey, TValue>
    {

        /// <summary>Adapts an <see cref="IdentityMap{TKey,TValue}" /> to an <see cref="ICacheManager" />.</summary>
        private sealed class CacheManagerAdapter:
            ICacheManager
        {

            /// <summary>Constructor.</summary>
            private CacheManagerAdapter()
            {
            }

            /// <summary>Constructor.</summary>
            public CacheManagerAdapter(IdentityMap<TKey, TValue> map)
            {
                _Map=map;
            }

            /// <summary>Adds an element with the provided key and value to the <see cref="ICacheManager" />.</summary>
            public void Add(object key, object value)
            {
                _Map.Add((TKey)key, (TValue)value);
            }

            /// <summary>Removes all keys and values from the <see cref="ICacheManager" />.</summary>
            public void Clear()
            {
                _Map.Clear();
            }

            /// <summary>Determines whether the <see cref="ICacheManager" /> contains an element with the specified key.</summary>
            public bool ContainsKey(object key)
            {
                return _Map.Contains((TKey)key);
            }

            /// <summary>Removes the element with the specified key from the <see cref="ICacheManager" />.</summary>
            public bool Remove(object key)
            {
                return _Map.Remove((TKey)key);
            }

            /// <summary>Gets the value associated with the specified key.</summary>
            public bool TryGetValue(object key, out object value)
            {
                TValue v;
                bool ret=_Map.TryGetValue((TKey)key, out v);
                value=v;
                return ret;
            }

            /// <summary>Gets or sets the element with the specified key.</summary>
            public object this[object key]
            {
                get
                {
                    return _Map._Map[(TKey)key].Target;
                }
            }

            private IdentityMap<TKey, TValue> _Map;
        }

        /// <summary>Constructor.</summary>
        public IdentityMap()
        {
            _Map=new Dictionary<TKey, WeakReference>();

            lock (SyncRoot)
                IdentityMap.MapList.Add(new WeakReference(_Map));
        }

        /// <summary>Adds the specified value to the map, with the specified key.</summary>
        public void Add(TKey key, TValue value)
        {
            lock (SyncRoot)
            {
                if (_Map.ContainsKey(key) && !Contains(key))
                    Remove(key);
                _Map.Add(key, new WeakReference(value));
            }
        }

        /// <summary>Remove all keys and values from the map.</summary>
        public void Clear()
        {
            lock (SyncRoot)
            {
                _Map.Clear();
            }
        }

        /// <summary>Determines whether the map contains a value with the specified key.</summary>
        public bool Contains(TKey key)
        {
            TValue tmp;
            return TryGetValue(key, out tmp);
        }



        /// <summary>Removes the value with the specified key from the map.</summary>
        public bool Remove(TKey key)
        {
            lock (SyncRoot)
                return _Map.Remove(key);
        }

        /// <summary>Gets the value associated with the specified key.</summary>
        public bool TryGetValue(TKey key, out TValue value)
        {
            bool ret=false;
            value=default(TValue);

            // Look up in the map for the associated weak reference
            WeakReference wr;
            lock (SyncRoot)
                ret=_Map.TryGetValue(key, out wr);

            if (ret)
            {
                try
                {
                    // If we found a weak reference, we try to get a strong reference
                    value=(TValue)wr.Target;

                    // If the object has been garbage collected, we can remove the weak reference
                    if (value==null)
                    {
                        Remove(key);
                        ret=false;
                    }
                } catch (InvalidOperationException)
                {
                    ret=false;
                }
            }

            return ret;
        }

        /// <summary>Gets an object that can be used to synchronize access to the <see cref="IdentityMap{TKey,TValue}" />.</summary>
        public object SyncRoot
        {
            get
            {
                return ((ICollection)_Map).SyncRoot;
            }
        }

        /// <summary>Gets an <see cref="ICacheManager" /> based on the <include file="../doc/Global.doc.xml" path="doc/template[@name='IdentityMap']/*" />.</summary>
        public ICacheManager CacheManager
        {
            get
            {
                if (_CacheManager==null)
                    _CacheManager=new CacheManagerAdapter(this);
                return _CacheManager;
            }
        }

        /// <summary>The internal map.</summary>
        private Dictionary<TKey, WeakReference> _Map;
        /// <summary>The associated cache manager adapter.</summary>
        private CacheManagerAdapter _CacheManager;

    }

    /// <summary>The clean up thread implementation.</summary>
    internal static class IdentityMap
    {

        /// <summary>The clean up thread implementation.</summary>
        private static void CleanUp()
        {
            for (; ; )
            {
                // We first remove all the unreferenced Identity Maps from the list
                lock (((ICollection)MapList).SyncRoot)
                    MapList.RemoveAll(
                        delegate(WeakReference wr) {
                            return !wr.IsAlive;
                        }
                    );

                try
                {
                    foreach (WeakReference mwr in MapList)
                    {
                        // For every referenced Identity Map...
                        IDictionary map=(IDictionary)mwr.Target;
                        if (map!=null)
                        {
                            // We remove unreferenced values
                            try
                            {
                                foreach (DictionaryEntry de in map)
                                    lock (((ICollection)map).SyncRoot)
                                        if (!((WeakReference)de.Value).IsAlive)
                                            map.Remove(de.Key); // Raises an InvalidOperationException
                            } catch (InvalidOperationException)
                            {
                                // map has been modified...
                            }

                            Thread.Sleep(0);
                        }
                    }

                    Thread.Sleep(2000);
                } catch (InvalidOperationException)
                {
                    // _MapList has been modified...
                }
            }
        }

        /// <summary>Static thread initialisation.</summary>
        /// <remarks>A static constructor would violate the FxCop InitializeReferenceTypeStaticFieldsInline performance rule (CA1810).</remarks>
        private static Thread InitCleanUpThread()
        {
            Thread ret=_CleanUpThread;

            if (ret==null)
            {
                ret=new Thread(new ThreadStart(CleanUp));
                ret.IsBackground=true;
                ret.Name=IdentityMap.CleanUpThreadName;
                ret.Priority=ThreadPriority.Lowest;
                ret.Start();
            }

            return ret;
        }

        /// <summary>The list of all identity maps.</summary>
        internal static List<WeakReference> MapList=new List<WeakReference>();
        /// <summary>The clean up thread.</summary>
        private static Thread _CleanUpThread=InitCleanUpThread();

        public static string CleanUpThreadName="Salamanca Identity Maps Clean Up Thread";
    }

}
